package com.woniu.controller;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.jwt.JWTUtil;
import com.woniu.common.Assert;
import com.woniu.common.SmsTelephoneUtil;
import com.woniu.enumeration.BusinessCode;
import com.woniu.enumeration.UserStatusType;
import com.woniu.pojo.User;
import com.woniu.redis.RedisConst;
import com.woniu.service.UserService;
import com.woniu.util.JwtUtils;
import com.woniu.vo.LoginRequest;
import com.woniu.vo.RegisterRequest;
import com.woniu.vo.UserVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Delete;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.*;

import static cn.hutool.jwt.JWTUtil.createToken;

@RestController
@RequestMapping("/user")
@Slf4j
@Api(tags = "用户")
public class UserController {
    @Autowired
    private SmsTelephoneUtil smsTelephoneUtil;
    @Autowired
    private UserService userService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 发件人
    @Value("${spring.mail.username}")
    private String from;

    @Autowired
    private JavaMailSender javaMailSender;

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private JwtUtils jwtUtils;


    @PostMapping("/login")
    @ApiOperation(value = "登录验证")
    public ResponseEntity<?> login(@RequestBody LoginRequest param,
                                   HttpServletRequest httpServletRequest) {
        // 1.调用业务层进行登录
        ResponseEntity<?> responseEntity = userService.login(param);

        // 2.如果登录成功 创建Token
        /**
         * 说明：
         *  login.getStatusCode()可以获取 HttpStatus中的响应码信息
         *  HttpStatus是枚举类，里面有响应码的常量，如：OK、NOT_FOUND等，例如相应成功是200
         *
         * */
        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            // 登录成功，创建token
            User user = (User)responseEntity.getBody();
            return createToken(httpServletRequest,user);
        }

        // 登录失败，返回错误信息
        return responseEntity;
    }

    /**
     * 生成Token，封装UserVo
     *     返回前端的标准格式 ： UserVo
     * */
    private ResponseEntity<UserVo> createToken(HttpServletRequest request,User user) {
        // 生成Token
        HashMap<String, Object> map = new HashMap<>();
        map.put("userId", user.getId());
        map.put("userName", user.getAccount());
        map.put("ip", jwtUtils.getRequestIP(request));
        String token = JwtUtils.generateJwt(map);

        // 将其存放直redis中，过期时间为 1天
        String key = RedisConst.USER_TOKEN_PREFIX + ":" + user.getId();
        stringRedisTemplate.opsForValue().set(key, token, Duration.ofDays(1));

        // 封装UserVo对象
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user,userVo);

        // 将token添加之响应头
        return ResponseEntity.ok()
                .header("Authorization", token)
                .header("Access-Control-Expose-Headers", "Authorization") // 此行代码意义，解决前端跨域问题。默认存在跨域情况时，前端无法读取请求头信息
                .body(userVo);
    }



    /**
     * 图片验证码
     * */
    @GetMapping("/code")
    @ApiOperation(value = "图片验证码")
    public ResponseEntity generateCode() throws IOException {
        // 创建缓存图片，指定高宽
        int width = 110, height = 45;
        //参数3：图片的类型，使用RGB类型
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        //3) 获取画笔对象
        Graphics graphics = image.getGraphics();
        //4) 设置画笔颜色，并且填充矩形区域
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, height);
        //创建一个StringBuider对象
        StringBuilder sb = new StringBuilder();
        //5) 从字符数组中随机得到字符
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        char[] arr = characters.toCharArray();
        Random random = new Random();
        for (int i = 0; i < 4; i++) {
            //随机从0到长度之间去取索引
            int index = random.nextInt(arr.length);
            //得到1个字符
            char c = arr[index];
            sb.append(c);
            //6) 设置字体，大小为22，设置字的颜色随机
            graphics.setFont(new Font(null, Font.BOLD + Font.ITALIC, 22));
            graphics.setColor(getColor());
            //7) 将每个字符画到图片，位置：5+(i*23), 25
            //将字符转成字符串
            graphics.drawString(String.valueOf(c), 5 + (i * 23), 25);
        }
        //8) 画干扰线8条线，线的位置是随机的，x范围在width之中，y的范围在height之中。
        for (int i = 0; i < 10; i++) {
            graphics.setColor(getColor());
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            graphics.drawLine(x1, y1, x2, y2);
        }
        //9) 将缓存的图片输出到响应输出流中
        //参数1：要输出的缓存图片，参数2：格式，参数3：输出到哪个流中
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "jpeg", outputStream);
        //随机生成一个key，前面加上code::
        String key = RedisConst.USER_VALIDATE_CODE + ":" + UUID.randomUUID().toString().replace("-", "");
        //得到验证码，将生成的字符串放到Redis中，键随机生成，10分钟过期
        stringRedisTemplate.opsForValue().set(key, sb.toString(), Duration.ofMinutes(10));
        log.info("生成的验证码为：{}，Redis中的key是：{}", sb, key);
        Base64.Encoder encoder = Base64.getEncoder();
        String base64Img = "data:image/jpeg;base64," + encoder.encodeToString(outputStream.toByteArray());
        //返回的数据包含验证码的key和图片
        Map<String, String> data = new HashMap<>();
        data.put("key", key);  //redis中键
        data.put("base64Img", base64Img);  //图片
        //返回key和图片的base64的编码
        return ResponseEntity.ok(data);
    }

    /**
     * 退出登录业务
     * 销毁 JWT token，删除Redis中token
     * */
    @DeleteMapping("/logout")
    @ApiOperation(value = "退出登录")
    public ResponseEntity<String> logout(@RequestHeader("Authorization") String token,
                                         HttpServletResponse response) {
        log.info("token的值为{}", token);
        token = jwtUtils.getToken(token);
        // 或如 token 对应的key，然后在redis删除
        Object userId = JwtUtils.parseJwt(token).get("userId");
        String key = RedisConst.USER_TOKEN_PREFIX + ":" + userId;
        log.info("删除的key值{}", key);
        stringRedisTemplate.delete(key);

        // 清除缓存
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "0");
        return ResponseEntity.ok("退出成功");
    }




    /**
     * 手机号发短信
     * */
    @GetMapping("/sendSms/{telephone}")
    @ApiOperation(value = "手机号发短信")
    public ResponseEntity<String> sendSms(@PathVariable String telephone) {
        // 生成随机四位手机验证码
        String code = RandomUtil.randomNumbers(4);
        log.info("手机验证码是：{}", code);

        //1.调用阿里云短信服务，发送短信
//        String result = smsTelephoneUtil.sendSms(telephone, code);
        String result = "OK";
        Assert.error(!result.contains("OK"), BusinessCode.SMS_SEND_ERROR);

        // 2.验证码存放redis，十分钟过期
        String key = RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone;
        stringRedisTemplate.opsForValue().set(key, code, Duration.ofMinutes(10));

        // 隐藏手机号，1358****5936，返回发送成功
        String hideTelephone = "给手机号：" + telephone.substring(0, 3) + "****" + telephone.substring(7)+ "成功发送验证码";
        return ResponseEntity.ok(hideTelephone);
    }


    /**
     * 短信登陆功能
     * 通过手机号查询用户是否存在
     * */
    @PostMapping("/smsLogin")
    @ApiOperation(value = "短信登陆")
    public ResponseEntity smsLogin(@RequestBody LoginRequest params,
                                   HttpServletRequest request) {
        // 获取手机号
        String telephone = params.getTelephone();
        // redis中获取验证码
        String key = RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone;
        String code = stringRedisTemplate.opsForValue().get(key);

        // 判断是否过期
        Assert.error(code == null, BusinessCode.INVALID_VERIFICATION);

        // 判断验证码是否正确
        Assert.error(!code.equalsIgnoreCase(params.getCode()), BusinessCode.VERIFICATION_ERROR);

        // 验证通过，删除验证码
        stringRedisTemplate.delete(key);

        // 查询用户
        User user = userService.findByTelephone(telephone);

        // 如果用户存在，判断状态是否正常
        Assert.error(Objects.nonNull(user) &&
                !user.getState().equals(UserStatusType.ACTIVE.getKey()), BusinessCode.USER_FROZEN_ERROR);

        // 用户不存在创建新用户
        if (user == null) {
            // 用户不存在，注册用户
            user = new User();
            user.setMobile(telephone);
            user.setAccount(telephone);
            user.setPassword(params.getPassword());
            user.setNickname(telephone);
            user.setRealName(telephone);
            user.setMobile(telephone);
            user.setEmail(telephone);
            user.setState(UserStatusType.ACTIVE.getKey());
            userService.save(user);
        }

        return createToken(request,user);
    }

    /**
     *
     *
     * */
    @GetMapping("/send")
    @ApiOperation(value = "邮箱验证码")
    public ResponseEntity sendEmail(@RequestParam("address") String email) {
        // 判断邮箱是否注册
        Assert.error(userService.isEmailExist(email), BusinessCode.EMAIL_EXIST_ERROR);

        // 将消息封装到Map，传给MQ
        HashMap<String, Object> emailMap = new HashMap<>();
        emailMap.put("from", from);
        emailMap.put("to", email);
        emailMap.put("subject", "蜗牛书城，验证码");
        String emailCode = RandomUtil.randomNumbers(4);
        String context = "您的验证码是：" + emailCode +"，有效期15分钟";
        emailMap.put("content", context);

//        simpleMailMessage.setText(context); // 发送验证码
        // 将发送邮件操作放入到 消息队列执行
        rabbitTemplate.convertAndSend("email.queue",emailMap);


        log.info("验证码：{}，发送到{}", emailCode, email);

        // 将验证码存储redis，设置有效 15分钟
        stringRedisTemplate.opsForValue().set(RedisConst.EMAIL_ADDRESS_PREFIX + ":" + email, emailCode, Duration.ofMinutes(15));
        return ResponseEntity.ok("验证码以发送到：" + email + "邮箱，请查收！");
    }

    /**
     * 注册用户功能
     * */
    @PostMapping("/register")
    @ApiOperation(value = "用户注册")
    public ResponseEntity<UserVo> register(@RequestBody RegisterRequest params,
                                           HttpServletRequest request) {
        // 从redis获取验证码 进行判断
        String code = stringRedisTemplate.opsForValue().get(RedisConst.EMAIL_ADDRESS_PREFIX + ":" + params.getEmail());

        // 判断验证码是否过期
        Assert.error(code == null, BusinessCode.INVALID_VERIFICATION);

        // 判断验证码是否正确
        Assert.error(!code.equalsIgnoreCase(params.getValidCode()), BusinessCode.VERIFICATION_ERROR);

        // 判断用户名是否重复
        Assert.error(userService.isAccountExist(params.getAccount()), BusinessCode.USER_IS_EXIST);

        // 创建用户对象
        User user = new User();
        // 将注册信息复制到user对象
        BeanUtils.copyProperties(params, user);

        // 调用业务层进行用户注册
        User register = userService.register(user);

        // 注册后直接登录
        return createToken(request,register);
    }






    //写一个方法随机获取颜色
    private Color getColor() {
        Random random = new Random();
        //红，绿，蓝取值0-255，随机生成 0-255之间数
        int r = random.nextInt(256);
        int g = random.nextInt(256);
        int b = random.nextInt(256);
        return new Color(r, g, b);
    }

}
